package com.sprouting.personal.interceptor;

import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import com.sprouting.personal.interfaces.common.Constant;
import com.sprouting.personal.interfaces.common.ConstantPath;
import com.sprouting.personal.utils.CheckTool;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.ArrayList;
import java.util.List;

/**
 * sa-token 权限认证拦截器
 * 注意：如果在高版本 SpringBoot (≥2.6.x) 下注册拦截器生效，则需要额外添加 @EnableWebMvc 注解才可以使用。
 *
 * @author ：LX（长沙麓谷）
 * @date 创建时间： 2022/1/6 14:05
 */
@Configuration
public class AuthenticationInterceptor implements WebMvcConfigurer {

    /**
     * 放行的权限,不进行权限验证
     */
    @Value("#{'${release.url}'.split(',')}")
    private List<String> releaseUrlList;


    /**
     * 文件存储基础路径
     */
    @Value("${upload.data.base.path}")
    private String basePath;
    /**
     * 匹配静态资源
     */
    @Value("${match.static.resource.path}")
    private String matchStaticResourcePath;
    /**
     * 前端包打包后存放位置
     */
    @Value("${web.package.path}")
    private String webPackagePath;


    /**
     * 静态资源拦截处理，使得图片不需要经过nginx就可以直接查看
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        /*
            必须加"/"，不然映射不到
            registry.addResourceHandler("/asset/**") 匹配后，这个 asset 路径会被去掉

            使用步骤

                后端
                首先要在配置中将 release.url 配置 upload 的前缀不进行拦截，否则会被权限拦截器进行拦截，

                前端
                然后前端请求要匹配资源路径
                涉及的参数，upload 前端的 setting.js 中的 resourceURL 要配置好，资源的前缀
                config/index.js 下的 proxyTable 代理设置中的拦截 asset， setting.resourceURL 配置
                访问：http://127.0.0.1:8084/ps/upload/asset/book/images/1565738077504323584.png

        */

        /// 图片 访问地址：http://127.0.0.1:8084/upload/asset/1553764000786944000/notepad/images/1622282287736614912/1622282470587297792.png
        /*
          注意，这里是前端的代理图片后的地址，只有开发模式下才有用，到了生产的时候，vue配置的这些代理都已经失效，此时，也就不会用到这个东西。也就是如果开发环境
            地址是 /upload/桶名(后端图片的前缀)/图片地址.png
            生产则是 /桶名(后端图片的前缀)/图片地址.png
         */
        // 开发模式匹配的路径
        registry.addResourceHandler(matchStaticResourcePath).addResourceLocations("file:/" + basePath + "/");
        // 生产模式匹配的路径，就少了前面的
        registry.addResourceHandler("/asset/**").addResourceLocations("file:/" + basePath + ConstantPath.Base.BUCKET_PATH);
        // 还可以使用下面的class路径
        // registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");

        // 前端打包后的存放地址，不要有中文路径，千万注意，最坑的是有时候中文可以，有时候忽然又不行了
        if (!CheckTool.checkNull(webPackagePath)){
            registry.addResourceHandler("/**").addResourceLocations("file:/" + webPackagePath + "/");
        }
    }

    /**
     * 注册sa-token的注解拦截器，打开注解式鉴权功能
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 不拦截的路径
        if (releaseUrlList == null){
            releaseUrlList = new ArrayList<>();
        }

        // 1、注册Sa-Token的路由拦截器,打开注解式鉴权功能
        // 使用AOP模式，可以将注解写在任意层级，使用拦截器模式，只能把注解写在Controller层上。拦截器模式和AOP模式不可同时集成，否则会在Controller层发生一个注解校验两次的bug
        // registry.addInterceptor(new SaRouteInterceptor())
        //         // 不加 ps 这个前缀的话，会导致登录页面一直被拦截，非常麻烦
        //         .addPathPatterns(Constant.Base.SYS_PREFIX + "/**")
        //         .excludePathPatterns(releaseUrlList);
        //
        // // 2、注册注解拦截器，并排除不需要注解鉴权的接口地址 (与登录拦截器无关),添加了如下代码就可以使用注解鉴权了
        // // 使用拦截器模式，只能在Controller层进行注解鉴权，如需在任意层级使用注解鉴权，需要AOP注解鉴权
        // registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns(releaseUrlList)
        //         // 配置优先级
        //         // .order(100)
        // ;

        registry.addInterceptor(new SaInterceptor(handle -> {
            // 参数一：要匹配的path路由。参数二：要执行的校验函数。
            SaRouter
                    .match("/**")
                    // 排除掉的 path 列表，可以写多个
                    .notMatch("/error","/favicon.ico")
                    .notMatch(releaseUrlList)
                    // 排除掉 knife4j
                    .notMatch("/doc.html")
                    .check(r -> StpUtil.checkLogin());

            // 根据路由划分模块，不同模块不同鉴权。r 后面可以写任意代码
            // SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));

        })).addPathPatterns(Constant.Base.SYS_PREFIX + "/**").excludePathPatterns(releaseUrlList);
    }

    /**
     * 统一对 controller 添加前缀，如果放在配置文件中的 server.servlet.context-path ，那么静态资源也要这个前缀
     * @param configurer
     */
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        // 指定注解的方式
        configurer.addPathPrefix(Constant.Base.SYS_PREFIX,
                c -> c.isAnnotationPresent(Controller.class) || c.isAnnotationPresent(RestController.class));


        // 指定包的方式
        // configurer.addPathPrefix(Constant.Base.SYS_PREFIX, c -> c.getPackage().getName().contains("com.sprouting.personal.controller"));
    }
}
